﻿#region namespaces

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.BaseX
{
    internal sealed class DatabaseObjectManager
    {
        private readonly ConcurrentDictionary<long /*DatabaseId*/, DbItem> _idsToObject = new ConcurrentDictionary<long, DbItem>();
        private readonly ReaderWriterLockSlim _rwl = new ReaderWriterLockSlim();

        #region Read

        public bool Contains(long databaseId)
        {
            return _rwl.UseReadLock(() => !databaseId.IsDefault() && _idsToObject.ContainsKey(databaseId));
        }

        public DbItem Get(long id)
        {
            return _rwl.UseReadLock(() =>
                {
                    DbItem dbi;
                    return _idsToObject.TryGetValue(id, out dbi) ? dbi : null;
                });
        }

        public bool TryGet(long id, out DbItem dbi)
        {
            _rwl.EnterReadLock();
            try
            {
                return _idsToObject.TryGetValue(id, out dbi);
            }
            finally
            {
                _rwl.ExitReadLock();
            }
        }

        #endregion

        #region Write

        internal DbItem Store(DbItem dbItem)
        {
            return _rwl.UseWriteLock(() =>
                {
                    var id = dbItem.Id;
                    DbItem res;
                    if (_idsToObject.TryGetValue(id, out res))
                    {
                        return res;
                    }

                    _idsToObject[id] = dbItem;
                    return dbItem;
                });
        }

        public DbItem Store(IPersistable dbObj, IVisualProperties prop)
        {
            Debug.Assert(dbObj.DatabaseId == prop.BelongsToDatabaseId);
            return Store(new DbItem(dbObj.DatabaseId, dbObj, prop));
        }

        public void Store(ref IPersistable dbObj, ref IVisualProperties prop)
        {
            Debug.Assert(dbObj.DatabaseId == prop.BelongsToDatabaseId);
            var dbi = Store(new DbItem(dbObj.DatabaseId, dbObj, prop));

            dbObj = dbi.DatabasePersistable;
            prop = dbi.VisualProperties;
        }

        internal bool TryRemove(long id, out DbItem dbi)
        {
            _rwl.EnterWriteLock();
            try
            {
                return _idsToObject.TryRemove(id, out dbi);
            }
            finally
            {
                _rwl.ExitWriteLock();
            }
        }

        public bool TryRemove(long id)
        {
            DbItem dbi;
            return TryRemove(id, out dbi);
        }

        public void AddDatabaseObjectChangedListener(IPersistable obj, IDisposable changedListener)
        {
            _rwl.EnterWriteLock();
            try
            {
                DbItem dbi;
                if (!_idsToObject.TryGetValue(obj.DatabaseId, out dbi))
                {
                    throw new ArgumentException(string.Format("No DatabaseObject {0}", obj), "obj");
                }
                dbi.DatabaseObjectChangedListener = changedListener;
            }
            finally
            {
                _rwl.ExitWriteLock();
            }
        }

        public void AddVisualPropertiesChangedListener(IVisualProperties prop, IDisposable changedListener)
        {
            _rwl.EnterWriteLock();
            try
            {
                DbItem dbi;
                if (!_idsToObject.TryGetValue(prop.BelongsToDatabaseId, out dbi))
                {
                    throw new ArgumentException(string.Format("No VisualProperties {0}", prop), "prop");
                }
                dbi.VisualPropertiesChangedListener = changedListener;
            }
            finally
            {
                _rwl.ExitWriteLock();
            }
        }

        public void DisposeAlll()
        {
            _rwl.EnterWriteLock();
            try
            {
                foreach (DbItem dbi in _idsToObject.Values)
                {
                    if (dbi.DatabaseObjectChangedListener != null)
                    {
                        try
                        {
                            dbi.DatabaseObjectChangedListener.Dispose();
                        }
                        catch (ObjectDisposedException)
                        {
                        }
                    }
                    if (dbi.VisualPropertiesChangedListener != null)
                    {
                        try
                        {
                            dbi.VisualPropertiesChangedListener.Dispose();
                        }
                        catch (ObjectDisposedException)
                        {
                        }
                    }
                }
            }
            finally
            {
                _rwl.ExitWriteLock();
            }
        }

        #endregion

        #region Nested type: DbItem

        internal sealed class DbItem
        {
            public readonly IPersistable DatabasePersistable;
            public readonly long Id;
            public readonly IVisualProperties VisualProperties;
            public IDisposable DatabaseObjectChangedListener = null;
            public IDisposable VisualPropertiesChangedListener = null;

            public DbItem(long id, IPersistable databasePersistable, IVisualProperties visualProperties)
            {
                Id = id;
                DatabasePersistable = databasePersistable;
                VisualProperties = visualProperties;
            }

            public Tuple<IPersistable, IVisualProperties> ToTuple()
            {
                return Tuple.Create(DatabasePersistable, VisualProperties);
            }

            public DatabaseObjectActionEventArgs ToEventArgs()
            {
                return new DatabaseObjectActionEventArgs(DatabasePersistable, VisualProperties);
            }
        }

        #endregion
    }
}